{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Use of the static grid equivalents\n",
    "This tutorial gives a quick overview of the grid equivalents function. This script gives an example to reduce a grid area using the IEEE case9 grid."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Determination of grid areas\n",
    "\n",
    "The user can determine the grid area of interest (internal area), the grid area for the reduction (external area), and the boundary buses between them. The figure below schows an example of the grid with identified grid areas:\n",
    "\n",
    "- internal area (buses): [0, 3, 4, 8]\n",
    "- boundary buses: [4, 8] (boundary buses belong to the internal area)\n",
    "- external area (buses): [1, 2, 5, 6, 7] \n",
    "\n",
    "<img src=\"pics/grid areas.png\" alt=\"ALT\">\n",
    "\n",
    "In order to identify the user-defined area, the boundary buses and one of the internal buses must be provided as inputs.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Required inputs\n",
    "\n",
    "The most important inputs for grid equivalents are:\n",
    "- net: pandapower grid including power flow results\n",
    "- eq_type: the method used to determine the equivalent grid, e.g., \"rei\", \"ward\", \"xward\"\n",
    "- boundary_buses: indices of boundary buses (as a list) that divide the original grid into the internal areas and the external areas. Based on the figure above, boundary_buses = [4, 8] \n",
    "- internal_buses: bus indices (as a list), which are within the internal area. Based on the figure above, internal_buses = [0]. Just one of the internal buses is enough, the function will find and consider the remaining internal buses during the equivalent calculation. If 'internal_buses' is an empty list or None, the complete grid is treated as an external area."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example: REI-equivalent\n",
    "In the following, the reduction of the external grid (buses [1, 2, 5, 6, 7] of the grid case 9) in the figure above is shown.\n",
    "First, the necessary libraries need to be imported."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:21.287905Z",
     "start_time": "2025-10-20T06:14:17.684936Z"
    }
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "from pandapower.plotting.plotting_toolbox import set_line_geodata_from_bus_geodata\n",
    "from pandapower.networks.power_system_test_cases import case9\n",
    "from pandapower.toolbox.grid_modification import drop_lines\n",
    "from pandapower.create import create_dcline\n",
    "from pandapower.grid_equivalents import get_equivalent, get_boundaries_by_bus_zone_with_boundary_branches, set_bus_zone_by_boundary_branches\n",
    "from pandapower.run import runpp"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Subsequently, the IEEE case 9 grid model is created, and its power flow is calculated."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:23.699856Z",
     "start_time": "2025-10-20T06:14:21.292888Z"
    }
   },
   "outputs": [],
   "source": [
    "net = case9()\n",
    "net.sn_mva = 1.0\n",
    "runpp(net)\n",
    "net"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "Next, we define the input parameters according to the figure above and obtain the REI-equivalent."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:23.730891Z",
     "start_time": "2025-10-20T06:14:23.711086Z"
    }
   },
   "outputs": [],
   "source": [
    "# equivalent type\n",
    "eq_type = \"rei\"\n",
    "\n",
    "# boundary buses\n",
    "boundary_buses = [4, 8]\n",
    "\n",
    "# internal buses \n",
    "internal_buses = [0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Next, the equivalent function is called, and the grid reduction is executed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:24.928461Z",
     "start_time": "2025-10-20T06:14:23.736610Z"
    }
   },
   "outputs": [],
   "source": [
    "net_eq = get_equivalent(net, eq_type, boundary_buses, internal_buses)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, the grid equivalent is obtained: \"net_eq\" is the REI-equivalent grid model. We can compare the power flow results between the original grid \"net\" and the reduced grid \"net_eq\".  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:24.992832Z",
     "start_time": "2025-10-20T06:14:24.934969Z"
    }
   },
   "outputs": [],
   "source": [
    "print(\"--- power flow (original grid) ---\")\n",
    "display(net.res_bus)\n",
    "print(\"--- power flow (reduced grid) ---\")\n",
    "display(net_eq.res_bus)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It can be seen that the power flow results (*vm_pu*, *va_degree*) of the internal buses [0, 3, 4, 8] in both grids are the same , i.e., the equivalent calculation is successful. The *p_mw* and *q_mvar* values at the boundary buses [4, 8] are different due to the equivalent devices. During the grid reduction, additional equivalent devices (e.g., additional buses [9, 10, 11], impedance, shunts, etc.) are created, representing the interaction between the internal area and the external area, and maintaining the grid states in the internal area. We can see the additional shunts in the grid equivalent:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:25.117281Z",
     "start_time": "2025-10-20T06:14:25.084149Z"
    }
   },
   "outputs": [],
   "source": [
    "print(\"--- shunts (original grid) ---\")\n",
    "display(net.shunt)\n",
    "print(\"--- shunts (reduced grid) ---\")\n",
    "display(net_eq.shunt)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example: (X)Ward-equivalent"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the following, we demonstrate an example for the (x)ward-equivalent. We change the equivalent type and repeat the equivalent calculation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:25.786227Z",
     "start_time": "2025-10-20T06:14:25.232676Z"
    }
   },
   "outputs": [],
   "source": [
    "# equivalent type\n",
    "eq_type = \"ward\"   # for xward-equivalent: eq_type = \"xward\"\n",
    "\n",
    "# run equivalent calculation\n",
    "net_eq_ward = get_equivalent(net, eq_type, boundary_buses, internal_buses)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, the ward equivalent is obtained. We can verify the power flow results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:25.817524Z",
     "start_time": "2025-10-20T06:14:25.794418Z"
    }
   },
   "outputs": [],
   "source": [
    "print(\"--- power flow (original grid) ---\")\n",
    "display(net.res_bus)\n",
    "print(\"--- power flow (reduced grid) ---\")\n",
    "display(net_eq_ward.res_bus)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The power flow results (*vm_pu*, *va_degree*) for the internal buses [0, 3, 4, 8] in both grids are the same. The external area is represented by the addtional *ward* elements in the grid model \"net_eq_ward\", attached at the boundary buses [4, 8]: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:25.991079Z",
     "start_time": "2025-10-20T06:14:25.971533Z"
    }
   },
   "outputs": [],
   "source": [
    "print(\"--- ward (original grid) ---\")\n",
    "display(net.ward)\n",
    "print(\"--- ward (reduced grid) ---\")\n",
    "display(net_eq_ward.ward)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Defining the zones based on boundary branches\n",
    "\n",
    "For this example we will use the lines between the buses 4 ... 5 and 7 ... 8\n",
    "\n",
    "With the function *set_bus_zone_by_boundary_branches* we first define the zones of the grid based on this separation. Thereafter, with the function *get_boundaries_by_bus_zone_with_boundary_branches*, we obtain the boundary buses that we can use with the functions for obtaining the grid equivalents, as showed in previous sections."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:26.240152Z",
     "start_time": "2025-10-20T06:14:26.208245Z"
    }
   },
   "outputs": [],
   "source": [
    "boundary_branches = {\"line\": [2, 7]}\n",
    "set_bus_zone_by_boundary_branches(net, boundary_branches)\n",
    "buses, branches = get_boundaries_by_bus_zone_with_boundary_branches(net)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The dictionary *buses* contains the biundary buses, internal buses and external buses from the point of view of each of the zones."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:26.471990Z",
     "start_time": "2025-10-20T06:14:26.451084Z"
    }
   },
   "outputs": [],
   "source": [
    "buses"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The resulting separation can be seen in the figure below. The green buses are the nodes of the boundary lines that are part of the internal zone. The red buses are the nodes of the boundary lines that are in the external zone. The red dashed lines are the boundary lines."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:27.055082Z",
     "start_time": "2025-10-20T06:14:26.528932Z"
    }
   },
   "outputs": [],
   "source": [
    "zone = 1\n",
    "\n",
    "set_line_geodata_from_bus_geodata(net, overwrite=True)\n",
    "\n",
    "import geopandas as gpd\n",
    "import geojson\n",
    "\n",
    "fig, ax=plt.subplots(figsize=(7,11))\n",
    "\n",
    "net.bus.geo = net.bus.geo.apply(lambda x: geojson.loads(x))\n",
    "net.bus = gpd.GeoDataFrame(net.bus, geometry='geo')\n",
    "net.line.geo = net.line.geo.apply(lambda x: geojson.loads(x))\n",
    "net.line = gpd.GeoDataFrame(net.line, geometry='geo')\n",
    "\n",
    "net.line.plot(ax=ax)\n",
    "net.line.loc[list(boundary_branches[\"line\"])].plot(ax=ax, color='red', linestyle=\"--\")\n",
    "net.bus.plot(column=net.bus.zone.values, ax=ax,zorder=10)\n",
    "net.bus.loc[net.ext_grid.bus.values].plot(ax=ax, color='k', marker=\"s\", markersize=200)\n",
    "\n",
    "net.bus.loc[list(buses[zone][\"external\"])].plot(color='r', ax=ax, zorder=100, markersize=100)\n",
    "net.bus.loc[list(buses[zone][\"internal\"])].plot(color='g', ax=ax, zorder=100, markersize=100)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we can obtain the grid equivalent to reduce the zone 0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:27.662888Z",
     "start_time": "2025-10-20T06:14:27.073563Z"
    }
   },
   "outputs": [],
   "source": [
    "b_internal = [net.bus.loc[net.bus.zone==1].index[0]]\n",
    "net_eq = get_equivalent(net, \"rei\", buses[1][\"internal\"], b_internal)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:27.694539Z",
     "start_time": "2025-10-20T06:14:27.668925Z"
    }
   },
   "outputs": [],
   "source": [
    "net_eq.bus"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Grid equivalents with DC lines"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:27.898620Z",
     "start_time": "2025-10-20T06:14:27.803138Z"
    }
   },
   "outputs": [],
   "source": [
    "drop_lines(net, [2])\n",
    "create_dcline(net, 4, 5, 50, 0.5, 0.7, 1.0, 1.0)\n",
    "runpp(net)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:29.093192Z",
     "start_time": "2025-10-20T06:14:28.197134Z"
    }
   },
   "outputs": [],
   "source": [
    "net_eq = get_equivalent(net, \"rei\", [4,8], [0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-10-20T06:14:29.108370Z",
     "start_time": "2025-10-20T06:14:29.097961Z"
    }
   },
   "outputs": [],
   "source": [
    "net_eq.bus"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
